package com.md.annotation.aspectj;

import com.md.annotation.RLock;
import com.md.base.exception.ServerException;
import com.md.util.ClassUtils;
import com.md.util.RedisUtil;
import com.md.value.enums.ErrorEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.stream.IntStream;

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class RLockAspectj {

  private final RedisUtil redisUtil;

  /**
   * 环绕通知
   *
   * @param proceedingJoinPoint proceedingJoinPoint
   * @return 返回结果
   */
  @Around("@annotation(com.md.annotation.RLock)")
  public Object around(final ProceedingJoinPoint proceedingJoinPoint) {
    RLock annotation = ClassUtils.getAnnotation(proceedingJoinPoint, RLock.class);
    int waitTime = (int) annotation.timeUnit().toSeconds(annotation.waitTime());
    int leaseTime = (int) annotation.timeUnit().toSeconds(annotation.leaseTime());
    return redisUtil.lockWithRedisson(getKey(proceedingJoinPoint, annotation), () -> {
      try {
        return proceedingJoinPoint.proceed();
      } catch (Throwable e) {
        log.error("Lock method execute error, cause by: {}", e.getMessage(), e);
        if (e instanceof RuntimeException) {
          throw (RuntimeException) e;
        } else {
          throw new RuntimeException(e.getMessage(), e.getCause());
        }
      }
    }, waitTime, leaseTime);
  }

  /**
   * 获取key
   *
   * @param proceedingJoinPoint proceedingJoinPoint
   * @return redis缓存key
   */
  public String getKey(final ProceedingJoinPoint proceedingJoinPoint,
                       final RLock annotation) {
    String el = annotation.key();
    ExpressionParser expressionParser = new SpelExpressionParser();
    Expression expression = expressionParser.parseExpression(el);
    EvaluationContext context = new StandardEvaluationContext();
    String[] parameterNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
    Object[] args = proceedingJoinPoint.getArgs();
    IntStream.range(0, parameterNames.length).forEach((i) -> {
      context.setVariable(parameterNames[i], args[i]);
    });
    Object value;
    try {
      value = expression.getValue(context);
    } catch (EvaluationException var11) {
      value = el;
    }
    if (ObjectUtils.isEmpty(value)) {
      log.error("获取锁失败, 锁 KEY 值不允许为 NULL.");
      throw ServerException.of(ErrorEnum.REQUEST_LOCK_FAILED);
    } else {
      log.info(String.join(":", annotation.prefix(), String.valueOf(value)));
      return String.join(":", annotation.prefix(), String.valueOf(value));
    }
  }
}
